// Copyright (c) 2010 Thomas Stuefe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#pragma managed(push, off)
#include <windows.h>
#include <string.h>
#include "sqlite3.h"
#include "internals.h"
#pragma managed(pop)

#include <vcclr.h>

#include "Library.h"
#include "TrcLevel.h"
#include "DatabaseImpl.h"
#include "SqliteException.h"

using namespace System;

namespace zencat {
namespace sqlite {

#define INITALIZE_IF_NEEDED \
	if (!_initialized) { \
		Sqlite::Initialize(); \
	}

	IDatabase^ Sqlite::OpenDatabase(System::String^ databaseFile) {

		INITALIZE_IF_NEEDED;

		if (databaseFile == nullptr || databaseFile->Length == 0) {
			trcError(L"Invalid argument: databaseFile");
			throw gcnew ArgumentException(gcnew String(L"Invalid argument: databaseFile"));
		}

		const bool willCreate = ! System::IO::File::Exists(databaseFile);

		pin_ptr<const wchar_t> pinned_databaseFile = PtrToStringChars(databaseFile);
		const wchar_t* const databaseFile0 = pinned_databaseFile;

		trcInfo(L"Opening database \"%s\" ...", databaseFile0);

		sqlite3* dbHdl;
		const int rc = ::sqlite3_open16(databaseFile0, &dbHdl);
		
		if (rc != SQLITE_OK) {
			if (dbHdl) {
				::sqlite3_close(dbHdl);
			}
			trcSqliteError(rc, NULL, L"Failed to open database file \"%s\"", databaseFile0);
			throw gcnew SqliteException(nullptr, nullptr, rc, NULL, L"Failed to open Database.");
		} else {
			trcInfo(L"Database \"%s\" opened.", databaseFile0);
		}

		DatabaseImpl^ dbObj = gcnew DatabaseImpl(dbHdl, databaseFile, willCreate);
		return dbObj;

	}

	TrcLevel Sqlite::TraceLevel::get() {
		asrt(g_traceLevel >= (int)TrcLevel::Off && 
			g_traceLevel <= (int)TrcLevel::Verbose);
		return (TrcLevel) g_traceLevel;
	}

	void Sqlite::TraceLevel::set(TrcLevel lvl) {
		changeTraceLevel( (int) lvl);
	}

	TrcDestination Sqlite::TraceDestination::get() {
		return (TrcDestination) getTraceDestination();
	}

	void Sqlite::TraceDestination::set(TrcDestination v) {
		setTraceDestination( (int) v);
	}

	String^ Sqlite::TraceFileName::get() {
		const wchar_t* const f = getTraceFileName();
		return f ? gcnew String(f) : nullptr;
	}

	void Sqlite::TraceFileName::set(String^ v) {
		pin_ptr<const wchar_t> pinned = PtrToStringChars(v);
		const wchar_t* const v0 = pinned;
		setTraceFileName(v0);
	}

	int Sqlite::SqliteVersionNumber::get () {
		return SQLITE_VERSION_NUMBER;
	}

	String^ Sqlite::SqliteVersionString::get () {
		return gcnew String(WIDEN(SQLITE_VERSION));
	}

	void Sqlite::Initialize() {

		if (_initialized) {
			trcError(L"Already initialized.");
			 
			return;
		}

		trcVerbose(L"Initializing Sqlite Library...");

		const int rc = ::sqlite3_initialize();
		
		if (rc != SQLITE_OK) {
			trcSqliteError(rc, NULL, L"Sqlite initialization failed.");
			throw gcnew SqliteException(nullptr, nullptr, rc, NULL, L"Sqlite initialization failed.");
		}

		_initialized = true;

		trcVerbose(L"Sqlite Library initialized successfully.");

		asrt(SQLITE_VERSION_NUMBER == ::sqlite3_libversion_number());

	}

	void Sqlite::Shutdown() {
	
		if (!_initialized) {
			trcError(L"Ignoring shutdown call - already shut down.");
			return;
		}

		const int rc = ::sqlite3_shutdown();
		
		if (rc != SQLITE_OK) {
			trcSqliteError(rc, NULL, L"Sqlite shutdown failed.");
			throw gcnew SqliteException(nullptr, nullptr, rc, NULL, L"Sqlite shutdown failed.");
		}

		_initialized = false;

	}

	/// <summary>
	/// Trace a line to trace system.
	/// This can be used to interleave context information with internal sqlite tracing.
    /// </summary>
	void Sqlite::TraceLine(String^ line) {
		pin_ptr<const wchar_t> pinned = PtrToStringChars(line);
		const wchar_t* const v0 = pinned;
		trcInfo(L"%s", v0);
	}


} // end namespace sqlite

} // end namespace zencat